package com.qlshouyu.gateway.client.reactor.starter;

import com.qlshouyu.gateway.client.Bootgateway;
import com.qlshouyu.gateway.client.exception.NotFoundException;
import com.qlshouyu.gateway.client.exception.ServiceInstanceException;
import com.qlshouyu.gateway.client.model.Application;
import com.qlshouyu.gateway.client.model.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.*;
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.core.NestedRuntimeException;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;

public class LoadBalancerBootgateway {
    private final static Logger LOGGER = LoggerFactory.getLogger(LoadBalancerBootgateway.class);
    private LoadBalancerClientFactory clientFactory;
    private String lbAddr;
    private Bootgateway bootgateway;

    public LoadBalancerBootgateway(LoadBalancerClientFactory clientFactory, String lbAddr, Bootgateway bootgateway) {
        this.clientFactory = clientFactory;
        this.lbAddr = lbAddr;
        this.bootgateway = bootgateway;
    }


    /**
     * 创建应用
     *
     * @param application
     * @return
     */
    public Mono<Application> createApplication(Application application) {
        return this.choose().map(addr -> this.bootgateway.createApplication(addr, application));
    }

    /**
     * 根据应用编码获取应用信息
     *
     * @param code
     * @return
     */
    public Mono<Application> getApplicationByCode(String code) {
        return this.choose().map(addr -> this.bootgateway.getApplicationByCode(addr, code));
    }


    public Flux<Service> getServiceList() throws Exception {
        return this.choose().flatMapIterable(addr -> this.bootgateway.getServiceList(addr));
    }

    private Mono<String> choose() {
        String serviceId = verifyAndGetInstanceName();
        ReactorLoadBalancer<ServiceInstance> loadBalancer = this.clientFactory.getInstance(serviceId,
                ReactorServiceInstanceLoadBalancer.class);
        if (loadBalancer == null) {
            LOGGER.error("Unable to find instance for " + serviceId);
            return Mono.error(new ServiceInstanceException("Unable to find instance for " + serviceId));
//            throw new NotFoundException("Unable to find instance for " + serviceId);
        }
        return loadBalancer.choose().map(response -> {
            if (!response.hasServer()) {
                LOGGER.error("Unable to find instance for !response.hasServer()" + serviceId);
                throw new ServiceInstanceException("Unable to find instance for " + serviceId);
            }
            ServiceInstance retrievedInstance = response.getServer();
            String overrideScheme = retrievedInstance.isSecure() ? "https" : "http";
            String addr = overrideScheme + "://" + retrievedInstance.getHost() + ":" + retrievedInstance.getPort();
            if (LOGGER.isInfoEnabled()) {
                LOGGER.info("getApplicationByCode from: {}", addr);
            }
            return addr;
        });
    }

    private String verifyAndGetInstanceName() {
        URI url = null;
        try {
            url = new URI(this.lbAddr);
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
        if (url == null || !"lb".equals(url.getScheme())) {
            throw new ServiceInstanceException("Service id error");
        }
        return url.getHost();
    }
}
